﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using System.ServiceModel;
using System.Threading.Tasks;
using System.IO;


namespace DDFileSyncPlugin
{
    class Program
    {
        private static FileSyncServiceHost fileServiceHost;
        private static SyncType syncType = SyncType.SyncOff;
      
        private static bool ENABLE_SCHEDULER = false;
        public const string CUSTOM_DATE_FORMAT_SHORT = "dMMMyyyy";
        #region Sync constants
        private const string LOGS_CONTAINER_NAME_CONFIG = "com.dynamicdeploy.ddfilesync.LogsContainer";
        private static string LOGS_CONTAINER_NAME = "ddfilesynclogs";
        #endregion
        #region constants for scheduler
        private const string ENABLE_SCHEDULER_CONFIG = "com.dynamicdeploy.ddfilesync.EnableScheduler";
    

        internal const int TIMEINSECSFORINFINITEINITWAIT = 60;
        internal const int BACKUP_INITIAL_WAIT_MILLIS = 300000;
   

        private const string REBOOT_TRACKER_FILE_NAME = "ddreboot.tracker";
        private static string REBOOT_TRACKER_FILE_PATH = "ddreboot.tracker";
        public static bool IsReboot = false;

        public static string DEPLOYMENT_ID = null;
        public static string ROLE_ID = null;
        public static string INSTANCE_ID = null;

        internal const string MYSQLPROCESSNAME = "mysqld";

        private static Task PERSISTENT_DRIVE_RUN_THREAD = null;
        #endregion
        static void Main(string[] args)
        {
            DefaultTraceListener drl;
            drl = (DefaultTraceListener)Trace.Listeners["Default"];
            drl.LogFileName = "DDFileSyncPlugin.log";

            drl.TraceOutputOptions = TraceOptions.DateTime;
            Trace.TraceInformation("=====================================================================================================================================");
            
            Trace.TraceInformation(string.Format("{0}\t{1} in working directory {2}", DateTime.Now.ToString(), "Starting DDFileSyncPlugin", Environment.CurrentDirectory));

            try
            {
                //if (RoleEnvironment.IsAvailable)
                //{
                Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Start SetupConfigurationSettingPublisher()"));
                SetupConfigurationSettingPublisher();
                Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "End SetupConfigurationSettingPublisher()"));
                // Register event handler for roleinstance stopping  and changed events
                if (RoleEnvironment.IsAvailable)
                {
                    RoleEnvironment.Stopping += new EventHandler<RoleEnvironmentStoppingEventArgs>(RoleEnvironment_Stopping);
                    RoleEnvironment.Changed += new EventHandler<RoleEnvironmentChangedEventArgs>(RoleEnvironment_Changed);

                    DEPLOYMENT_ID = RoleEnvironment.DeploymentId;
                    INSTANCE_ID = RoleEnvironment.CurrentRoleInstance.Id;
                    ROLE_ID = RoleEnvironment.CurrentRoleInstance.Role.Name;
                }
                else
                {
                    DEPLOYMENT_ID = System.Guid.NewGuid().ToString("N");
                    INSTANCE_ID = "1";
                    ROLE_ID = Environment.MachineName;
                }

                bool fileCreated;
                REBOOT_TRACKER_FILE_PATH = FileUtils.CreateRebootTrackerFile(REBOOT_TRACKER_FILE_NAME, out fileCreated);
                IsReboot = !fileCreated;

                SyncService.FillEnvironmentVariables();

            
                MountPersistentDrives();
                InitScheduler();
                ////Initialize Persistent Drives
                //Task t1 = new Task(() =>
                //    {
                //        MountPersistentDrives();
                //    });
                

                ////Init MySqlScheduler
                //Task t2 = t1.ContinueWith((antecedent) =>
                //    {
                //        InitScheduler();
                //    }
                //);
                //Initialize
                Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Start InitializeSyncService()"));
                InitializeSyncService();
                Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "End InitializeSyncService()"));

                Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Start RunSyncServices()"));
                RunSyncServices();
                Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "End RunSyncServices()"));

                Task t = new Task(() =>
                {
                    BatchScriptsExecutor.GenerateTimersFromConfiguration(3);
                });

                try
                {
                    t.Start();

                }
                catch (Exception ex)
                {
                    Trace.TraceError("Error initializing batch schedulers. " + ex.Message);
                }

                System.Threading.Tasks.Task.Factory.StartNew(() =>
                {
                    try
                    {
                        while (true)
                        {

                            Trace.TraceInformation("Starting log file upload thread.");
                            Thread.Sleep(TimeSpan.FromSeconds(int.Parse(SyncService.GetConfigValueAsString("com.dynamicdeploy.ddfilesync.SyncCheckWithBlobInSecs")) * 10));
                            UploadLogFile();
                        }
                    }
                    catch (Exception ex)
                    {

                        Trace.TraceInformation("Error uploading log file " + ex.Message);
                    }
                });


             

                while (true)
                {
                    Thread.Sleep(TimeSpan.FromSeconds(300));


                }
                //}
                //else
                //{
                //    Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Role environment not available. Please run it in Azure."));
                //}

            }
            catch (Exception ex)
            {

                Trace.TraceError("Error in file sync " + ex.Message);

            }
        }

        #region Sync Services
        private static void RunSyncServices()
        {
            // This is a sample worker implementation. Replace with your logic.
            Trace.TraceInformation("Run entry point called", "Information");
            try
            {
                if (syncType != SyncType.SyncOff)
                {
                    if (syncType != SyncType.PeriodicSyncToStorageOnly)
                    {
                        System.Threading.Tasks.Task.Factory.StartNew(() =>
                        {
                            Trace.TraceInformation("Starting File Service Host");
                            StartFileSyncServiceHost();
                            Trace.TraceInformation("Started File Service Host");
                        });

                    }

                    if (syncType != SyncType.ReplicationOnly)
                    {
                        System.Threading.Tasks.Task.Factory.StartNew(() =>
                        {
                            try
                            {
                                Trace.TraceInformation("Starting Sync Service Continuously");
                                SyncService.Instance.RunContinuously();
                                Trace.TraceInformation("Started Sync Service Continuously");

                            }
                            catch (Exception ex)
                            {

                                Trace.TraceError("Error starting sync engine (RunContinuously) " + ex.Message);
                            }
                        }
                      );
                    }

                }//if
            }
            catch (Exception ex)
            {

                Trace.TraceError("Error in Run() " + ex.Message);
            }


        }

        private static void InitializeSyncService()
        {
            syncType = GetConfiguredSyncType();
            Trace.TraceInformation("Running with SyncType=" + syncType.ToString());
            if (syncType != SyncType.SyncOff)
            {
                try
                {
                    bool testing = bool.Parse(SyncService.GetConfigValueAsString("com.dynamicdeploy.ddfilesync.LocalTesting"));

                    if (testing)
                    {
                        Trace.TraceInformation("Creating test directories.");
                        CreateTestDirs();
                        Trace.TraceInformation("Created test directories.");

                    }



                    while (true)
                    {

                        if (SyncService.Instance.OnStart(null))
                        {
                            Trace.TraceInformation("Initialized Sync Service");
                            return;
                        }
                        else
                        {
                            Trace.TraceError("Some Directories were not found. Will go in an infinite loop till directories are available. Will wait in 60 sec rounds.");
                            Thread.Sleep(TimeSpan.FromSeconds(60));
                            UploadLogFile();

                        }

                    }
                }
                catch (Exception ex)
                {

                    Trace.TraceError("Error starting sync engine InitializeSyncService " + ex.Message);
                }

            }
            else
            {
                Trace.TraceInformation("Synchronization is off. Exitting application.");
                // Environment.Exit(-1);

            }

        }

        private static void CreateTestDirs()
        {
            try
            {
                string appRoot = Environment.GetEnvironmentVariable("RoleRoot") + @"\sitesroot";
                if (!System.IO.Directory.Exists(appRoot))
                {
                    // May be WorkerRole
                    appRoot = Environment.GetEnvironmentVariable("RoleRoot") + @"\approot";
                }

                Trace.TraceInformation("App Root " + appRoot);
                Trace.TraceInformation("Setting directory permission for " + appRoot);
                FileUtils.SetFileSystemRights(appRoot, System.Security.AccessControl.FileSystemRights.CreateDirectories);
                Trace.TraceInformation("Set directory permission for " + appRoot);
                if (!Directory.Exists(Path.Combine(appRoot, "ddtemp1")))
                {
                    Directory.CreateDirectory(Path.Combine(appRoot, "ddtemp1"));

                }
                FileUtils.SetFileSystemRights(Path.Combine(appRoot, "ddtemp1"), System.Security.AccessControl.FileSystemRights.CreateFiles | System.Security.AccessControl.FileSystemRights.Delete | System.Security.AccessControl.FileSystemRights.DeleteSubdirectoriesAndFiles | System.Security.AccessControl.FileSystemRights.Modify | System.Security.AccessControl.FileSystemRights.Synchronize | System.Security.AccessControl.FileSystemRights.Write);
                File.WriteAllText(Path.Combine(appRoot, "ddtemp1", "ddtemp1.txt"), "Test file");
                if (!Directory.Exists(Path.Combine(appRoot, "ddtemp2")))
                {
                    Directory.CreateDirectory(Path.Combine(appRoot, "ddtemp2"));


                }
                FileUtils.SetFileSystemRights(Path.Combine(appRoot, "ddtemp2"), System.Security.AccessControl.FileSystemRights.CreateFiles | System.Security.AccessControl.FileSystemRights.Delete | System.Security.AccessControl.FileSystemRights.DeleteSubdirectoriesAndFiles | System.Security.AccessControl.FileSystemRights.Modify | System.Security.AccessControl.FileSystemRights.Synchronize | System.Security.AccessControl.FileSystemRights.Write);
                File.WriteAllText(Path.Combine(appRoot, "ddtemp2", "ddtemp2.txt"), "Test file");
                Trace.TraceInformation("Created temp directories");
            }
            catch (Exception ex)
            {

                Trace.TraceError("Error in CreateTestDirs " + ex.Message);
            }

        }
        private static void StartFileSyncServiceHost()
        {

            try
            {
                Trace.TraceInformation("Starting file service host.");
                fileServiceHost = new FileSyncServiceHost()
                {
                    FileStorageAccount = CloudStorageAccount.Parse(SyncService.GetConfigValueAsString("com.dynamicdeploy.ddfilesync.DataConnectionString")),
                    DdSyncType = syncType
                };
                Trace.TraceInformation("Initialized FileStorageAccount with storage account " + fileServiceHost.FileStorageAccount.BlobEndpoint.AbsoluteUri);
                fileServiceHost.StartService(3);
                Trace.TraceInformation("Started file service host.");
            }
            catch (Exception ex)
            {

                Trace.TraceError("Critical Error starting file service host StartFileSyncServiceHost. Replication will not work. Exception " + ex.Message);
                throw ex;
            }

        }


        private static SyncType GetConfiguredSyncType()
        {
            SyncType st;
            if (!Enum.TryParse<SyncType>(SyncService.GetConfigValueAsString("com.dynamicdeploy.ddfilesync.SyncType"), out st))
            {

                Trace.TraceError("Could not parse SyncType. Turning synchronization off.");
                st = SyncType.SyncOff;

            }

            return st;

        }
        #endregion

        #region Role Environment
        private static void SetupConfigurationSettingPublisher()
        {
            // This code sets up a handler to update CloudStorageAccount instances when their corresponding
            // configuration settings change in the service configuration file.
            Trace.TraceInformation("Setting up configuration setting publishing");
            CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
            {
                // Provide the configSetter with the initial value
                configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));

                RoleEnvironment.Changed += (sender, arg) =>
                {
                    if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>()
                        .Any((change) => (change.ConfigurationSettingName == configName)))
                    {
                        // The corresponding configuration setting has changed, propagate the value
                        if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
                        {
                            // In this case, the change to the storage account credentials in the
                            // service configuration is significant enough that the role needs to be
                            // recycled in order to use the latest settings. (for example, the 
                            // endpoint has changed)
                            RoleEnvironment.RequestRecycle();
                        }
                    }
                };
            });
        }
        static void RoleEnvironment_Changed(object sender, RoleEnvironmentChangedEventArgs e)
        {
            try
            {
                Trace.TraceError("Roleinstance changed. Will not change synchronization type. Please reboot for changing the synchronization type.");
                if (e.Changes.Any(chg => chg is RoleEnvironmentConfigurationSettingChange))
                {
                    // Perform an action, for example, you can initialize a client, 
                    // or you can recycle the role
                    Trace.TraceError("Any change to configuration will trigger a recycle to maintain consistency");
                    RoleEnvironment.RequestRecycle();
                }

                //// Get the list of configuration changes
                //var settingChanges = e.Changes.OfType<RoleEnvironmentConfigurationSettingChange>();

                //foreach (var settingChange in settingChanges)
                //{
                //    if( settingChange.ConfigurationSettingName == 
                //    Trace.WriteLine(message, "Information");
                //}
            }
            catch (Exception ex)
            {

                Trace.TraceError("Error processing setting change." + ex.Message);

            }

        }

        static void RoleEnvironment_Stopping(object sender, RoleEnvironmentStoppingEventArgs e)
        {
            try
            {
                Trace.TraceWarning("Roleinstance stopping, hence terminating file synchronization.");


                Task t1 = new Task(() =>
                {
                   

                    Trace.TraceWarning("Running backup before stopping the instance.");
                    InitializeMySqlScheduler(false, true);
                    Trace.TraceWarning("Ran backup before stopping the instance.");

                });

                  Task t2 = new Task(() =>
                {
                Trace.TraceWarning("Running sync before stopping the instance.");
                SyncService.Instance.Sync();

                Trace.TraceWarning("Ran sync before stopping the instance.");

                });

                  t1.Start();
                  t2.Start();

                  Task.WaitAll(new Task[] {t1, t2}, TimeSpan.FromMinutes(5));
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error in RoleEnvironment_Stopping " + ex.Message);
            }
            finally
            {

                UploadLogFile();
            }
            Environment.Exit(-1);
        }
        #endregion

        #region Logs
        static void UploadLogFile()
        {

            try
            {

                string filePath = Path.Combine(Environment.CurrentDirectory, "DDFileSyncPlugin.log");
                if (File.Exists(filePath) && new FileInfo(filePath).Length > 0)
                {
                    string logsContainer = LOGS_CONTAINER_NAME;
                    if (!string.IsNullOrEmpty(SyncService.GetConfigValueAsString("com.dynamicdeploy.ddfilesync.LogsContainer")))
                    {
                        logsContainer = SyncService.GetConfigValueAsString("com.dynamicdeploy.ddfilesync.LogsContainer");
                    }
                    var logs = CloudStorageAccount.Parse(SyncService.GetConfigValueAsString("com.dynamicdeploy.ddfilesync.DataConnectionString")).CreateCloudBlobClient().GetContainerReference(logsContainer);
                    logs.CreateIfNotExist();
                    string customDT = DateTime.UtcNow.ToString(CUSTOM_DATE_FORMAT_SHORT).Replace(" ", ""); 
                    var error = logs.GetBlobReference(string.Format("{0}/{1}/{2}/{3}{4}.log", DEPLOYMENT_ID, RoleEnvironment.CurrentRoleInstance.Id, customDT, "DDFileSyncPlugin-", (DateTime.MaxValue - DateTime.UtcNow).Ticks.ToString("d19")));
                    error.Properties.ContentType = "text/plain";


                    Trace.TraceInformation("=====================================================================================================================================");

                    error.UploadFile(filePath, new BlobRequestOptions() { RetryPolicy = SyncService.RETRY_POLICY, Timeout = TimeSpan.FromSeconds(60) });
                    File.WriteAllText(filePath, string.Empty);
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError("Error uploading log file " + ex.Message);
            }
        }
        #endregion

        #region Backup Restore Scheduler

        private static void InitScheduler()
        {
            System.Threading.Tasks.Task t = new System.Threading.Tasks.Task(() =>
            {
               
                while (true)
                {
                    try
                    {

                        Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Initializing MySqlBackupServices()"));
                        if (InitializeMySqlScheduler(true, false))
                        {
                            Trace.TraceInformation("Initialized MySqlBackupServices. Ready to go!");
                            return;
                        }
                        else
                        {
                            Trace.TraceError("There was an issue initializing the MySql backup scheduler. Will go in an infinite loop till directories are available. Will wait " + TIMEINSECSFORINFINITEINITWAIT + "secs");
                            Thread.Sleep(TimeSpan.FromSeconds(TIMEINSECSFORINFINITEINITWAIT));
                            UploadLogFile();

                        }
                        Trace.TraceInformation(string.Format("{0}\t{1}", DateTime.Now.ToString(), "Initialized MySqlBackupServices()"));


                    }
                    catch (Exception ex)
                    {

                        Trace.TraceInformation("General exception whil starting MySqlSchedule task " + ex.Message);

                    }

                }

            });

            try
            {
                t.Start();

            }
            catch (Exception ex)
            {

                Trace.TraceInformation("General exception whil starting MySqlSchedule task " + ex.Message);


            }

        }
        private static MySqlBackupTrigger GetTriggerFromString(string configString)
        {
            string comStr = configString.ToLower();

            if (comStr == MySqlBackupTrigger.daily.ToString().ToLower())
            {
                return MySqlBackupTrigger.daily;

            }
            else if (comStr == MySqlBackupTrigger.halfday.ToString())
            {
                return MySqlBackupTrigger.halfday;

            }
            else if (comStr == MySqlBackupTrigger.halfhour.ToString())
            {
                return MySqlBackupTrigger.halfhour;

            }
            else if (comStr == MySqlBackupTrigger.hourly.ToString())
            {
                return MySqlBackupTrigger.hourly;

            }
            else if (comStr == MySqlBackupTrigger.monthly.ToString())
            {
                return MySqlBackupTrigger.monthly;

            }

            return MySqlBackupTrigger.daily;
        }

      

    
        private static bool InitializeMySqlScheduler(bool runRestore, bool runNow)
        {
            Trace.TraceInformation("Starting scheduler.");
            try
            {
                if(!string.IsNullOrEmpty(SyncService.GetConfigValueAsString(ENABLE_SCHEDULER_CONFIG)))
                {
                    ENABLE_SCHEDULER = bool.Parse(SyncService.GetConfigValueAsString(ENABLE_SCHEDULER_CONFIG));

                    if (ENABLE_SCHEDULER)
                    {
                        while (true)
                        {
                            if (FileUtils.IsProcessRunning(MYSQLPROCESSNAME))
                            {
                                Trace.TraceInformation("MySQL is ready.");
                                break;
                            }
                            else
                            {
                                Trace.TraceInformation("MySql has not started yet. Will go in an infinite loop till directories are available. Will wait " + TIMEINSECSFORINFINITEINITWAIT + "secs");
                                Thread.Sleep(TimeSpan.FromSeconds(TIMEINSECSFORINFINITEINITWAIT));
                                continue;
                            }

                        }


                        return MySqlBackupRestoreConfig.ScheduleAndGetConfigObjectsFromConfiguration(runRestore: runRestore, runNow:runNow);
                       
                    }
                    else
                    {

                        Trace.TraceInformation("Backup and Restore schedules are disabled.");
                        return true;
                    }

                }



            }
            catch (Exception ex)
            {

                 Trace.TraceError("General error in initializing MySqlBackup Scheduler " + ex.Message);

                return false;
            }

              Trace.TraceInformation("Started scheduler.");
            return true;
        }
        #endregion

        #region Persistent Drive
        public static void MountPersistentDrives()
        {
            CloudDriveManagerRole.ENABLE_PERSISTENT_DRIVES = bool.Parse(SyncService.GetConfigValueAsString("com.dynamicdeploy.ddfilesync.EnablePersistentDrives"));

            if (CloudDriveManagerRole.ENABLE_PERSISTENT_DRIVES)
            {
                try
                {

                    CloudDriveManagerRole persistentDriveManager = new CloudDriveManagerRole();


                    while (!persistentDriveManager.OnStart())
                    {
                        Trace.TraceError("There was an error mounting one of the persistent drives. Will go into infinite loop trying for seconds " + TIMEINSECSFORINFINITEINITWAIT);
                        Thread.Sleep(TimeSpan.FromSeconds(TIMEINSECSFORINFINITEINITWAIT));

                    }


                    PERSISTENT_DRIVE_RUN_THREAD = new Task(() =>
                        {
                            persistentDriveManager.Run();

                        }, TaskCreationOptions.LongRunning);

                    PERSISTENT_DRIVE_RUN_THREAD.Start();


                }
                catch (Exception ex)
                {

                    Trace.TraceError("Error mounting persistent drives. Quitting..." + ex.Message);

                    // Environment.Exit(-1);
                }

            }
            else
            {

                Trace.TraceInformation("Persistent drives are not enabled.");

            }

        }
        #endregion

    }
}
